/*-------------------------------------------------------------------------
 *
 * buffile.c
 *	  管理大型缓冲临时文件。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/storage/file/buffile.c
 *
 * NOTES:
 *
 * BufFiles 提供了一种非常不完整的 stdio 在虚拟文件（如由 fd.c 管理）之上的模拟。目前，我们仅支持 stdio 的缓冲 I/O 方面：只有当缓冲区被填满或清空时，低级文件的读取或写入才会发生。这对虚拟文件的好处比普通内核文件更大，因为减少接触虚拟文件的频率降低了打开/关闭文件描述符的“抖动”。
 *
 * 请注意，BufFile 结构是使用 palloc() 分配的，因此会在查询/事务结束时自动消失。由于底层虚拟文件是通过 OpenTemporaryFile 创建的，因此即使在 ereport(ERROR) 中止处理的情况下，文件的所有资源都必然会被清理。所需的数据结构是在 BufFile 创建时当前的 palloc 上下文中生成的，任何外部资源（如临时文件）由当时当前的 ResourceOwner 拥有。
 *
 * BufFile 还支持超出操作系统文件大小限制的临时文件（通过打开多个 fd.c 临时文件）。这是对大量数据进行排序和哈希连接的基本特性。
 *
 * BufFile 支持可以与其他后端共享的临时文件，作为并行执行的基础设施。这些文件需要作为所有参与者都连接的 SharedFileSet 的成员被创建。
 *
 * BufFile 还支持可以由单个后端使用的临时文件，当相应的文件需要在事务中存活并需要多次打开和关闭时。这些文件需要作为 FileSet 的成员被创建。
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "commands/tablespace.h"
#include "executor/instrument.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/buf_internals.h"
#include "storage/buffile.h"
#include "storage/fd.h"
#include "utils/resowner.h"

/*
 * 我们将 BufFiles 拆分为以千兆字节大小的段，无论 RELSEG_SIZE。
 * 原因是我们希望大型 BufFiles 能够在可用时分布在多个表空间中。
 */
#define MAX_PHYSICAL_FILESIZE	0x40000000
#define BUFFILE_SEG_SIZE		(MAX_PHYSICAL_FILESIZE / BLCKSZ)

/*
 * 这个数据结构表示一个缓冲文件，它由一个或多个物理文件组成
 * （每个文件通过由 fd.c 管理的虚拟文件描述符访问）。
 */
struct BufFile
{
	int			numFiles;		/* 集合中的物理文件数量 */
	/* 除最后一个外的所有文件长度均为 MAX_PHYSICAL_FILESIZE */
	File	   *files;			/* 分配的数组，包含 numFiles 条目 */

	bool		isInterXact;	/* 在事务中保持打开？ */
	bool		dirty;			/* 缓冲区是否需要写入？ */
	bool		readOnly;		/* 文件是否已设置为只读？ */

	FileSet    *fileset;		/* 用于基于文件集的段文件的空间 */
	const char *name;			/* 基于文件集的 BufFile 名称 */

	/*
	 * resowner 是用于基础临时文件的 ResourceOwner。（我们
	 * 不需要明确记住我们正在使用的内存上下文，
	 * 因为在创建后我们仅将数组重新分配为更大。）
	 */
	ResourceOwner resowner;

	/*
	 * “当前位置”是缓冲区在逻辑文件中的起始位置。
	 * BufFile 用户看到的位置为（curFile，curOffset + pos）。
	 */
	int			curFile;		/* 文件索引（0..n），当前的位置的一部分 */
	off_t		curOffset;		/* 当前位置的偏移量部分 */
	int			pos;			/* 缓冲区中下一个读取/写入位置 */
	int			nbytes;			/* 缓冲区中有效字节的总数 */
	PGAlignedBlock buffer;
};

static BufFile *fc_makeBufFileCommon(int fc_nfiles);
static BufFile *fc_makeBufFile(File fc_firstfile);
static void fc_extendBufFile(BufFile *fc_file);
static void fc_BufFileLoadBuffer(BufFile *fc_file);
static void fc_BufFileDumpBuffer(BufFile *fc_file);
static void fc_BufFileFlush(BufFile *fc_file);
static File fc_MakeNewFileSetSegment(BufFile *fc_file, int fc_segment);

/*
 * 创建 BufFile 并执行常见初始化。
 */
static BufFile *
fc_makeBufFileCommon(int fc_nfiles)
{
	BufFile    *fc_file = (BufFile *) palloc(sizeof(BufFile));

	fc_file->numFiles = fc_nfiles;
	fc_file->isInterXact = false;
	fc_file->dirty = false;
	fc_file->resowner = CurrentResourceOwner;
	fc_file->curFile = 0;
	fc_file->curOffset = 0L;
	fc_file->pos = 0;
	fc_file->nbytes = 0;

	return fc_file;
}

/*
 * 给定第一个基础物理文件创建 BufFile。
 * 注意：调用方必须在适当时设置 isInterXact。
 */
static BufFile *
fc_makeBufFile(File fc_firstfile)
{
	BufFile    *fc_file = fc_makeBufFileCommon(1);

	fc_file->files = (File *) palloc(sizeof(File));
	fc_file->files[0] = fc_firstfile;
	fc_file->readOnly = false;
	fc_file->fileset = NULL;
	fc_file->name = NULL;

	return fc_file;
}

/*
 * 添加另一个组件临时文件。
 */
static void fc_extendBufFile(BufFile *fc_file)
{
	File		fc_pfile;
	ResourceOwner fc_oldowner;

	/* 确保将文件与 BufFile 的资源所有者关联 */
	fc_oldowner = CurrentResourceOwner;
	CurrentResourceOwner = fc_file->resowner;

	if (fc_file->fileset == NULL)
		fc_pfile = OpenTemporaryFile(fc_file->isInterXact);
	else
		fc_pfile = fc_MakeNewFileSetSegment(fc_file, fc_file->numFiles);

	Assert(fc_pfile >= 0);

	CurrentResourceOwner = fc_oldowner;

	fc_file->files = (File *) repalloc(fc_file->files,
									(fc_file->numFiles + 1) * sizeof(File));
	fc_file->files[fc_file->numFiles] = fc_pfile;
	fc_file->numFiles++;
}

/*
 * 为一个新的临时文件创建一个 BufFile（如果写入超过
 * MAX_PHYSICAL_FILESIZE 字节，它将扩展为多个临时文件）。
 *
 * 如果 interXact 为真，则临时文件将在事务结束时不会自动删除。
 *
 * 注意：如果 interXact 为真，调用者最好在一个
 * 内存上下文中调用我们，并且其资源所有者将在
 * 事务边界之间存活。
 */
BufFile * BufFileCreateTemp(bool fc_interXact)
{
	BufFile    *fc_file;
	File		fc_pfile;

	/*
	 * 确保为 OpenTemporaryFile 设置了临时表空间。
	 * 调用者可能已经完成了这一步，但在这里双重检查似乎很有用。
	 * 完全不这样做会导致临时文件总是放置在默认表空间中，
	 * 这是一个相当难以检测的错误。如果调用者希望确保在
	 * 某个其他资源上下文中完成任何所需的目录访问，他们可能更愿意提前完成。
	 */
	PrepareTempTablespaces();

	fc_pfile = OpenTemporaryFile(fc_interXact);
	Assert(fc_pfile >= 0);

	fc_file = fc_makeBufFile(fc_pfile);
	fc_file->isInterXact = fc_interXact;

	return fc_file;
}

/*
 * 为给定 BufFile 的给定段构建名称。
 */
static void fc_FileSetSegmentName(char *fc_name, const char *fc_buffile_name, int fc_segment)
{
	snprintf(fc_name, MAXPGPATH, "%s.%d", fc_buffile_name, fc_segment);
}

/*
 * 为基于文件集的 BufFile 创建一个新的段文件。
 */
static File fc_MakeNewFileSetSegment(BufFile *fc_buffile, int fc_segment)
{
	char		fc_name[MAXPGPATH];
	File		fc_file;

	/*
	 * 可能有文件在崩溃重启之前剩余，且名称相同。
	 * 为了使 BufFileOpenFileSet() 不会对有多少段感到困惑，
	 * 如果下一个段号已经存在，我们将取消链接它。
	 */
	fc_FileSetSegmentName(fc_name, fc_buffile->name, fc_segment + 1);
	FileSetDelete(fc_buffile->fileset, fc_name, true);

	/* 创建新的段。 */
	fc_FileSetSegmentName(fc_name, fc_buffile->name, fc_segment);
	fc_file = FileSetCreate(fc_buffile->fileset, fc_name);

	/* FileSetCreate 会出错 */
	Assert(fc_file > 0);

	return fc_file;
}

/*
 * 创建一个 BufFile，可以被其他后端发现并以只读方式打开，
 * 这些后端使用相同的名称连接到同一个 SharedFileSet。
 *
 * 基于文件集的 BufFiles 的命名方案留给调用代码。
 * 该名称将作为一个或多个文件名的一部分出现在磁盘上，
 * 可能为管理员提供提示，说明哪个子系统正在生成
 * 临时文件数据。由于每个 SharedFileSet 对象由一个或
 * 多个唯一名称的临时目录支持，名称不会与
 * 不相关的 SharedFileSet 对象冲突。
 */
BufFile * BufFileCreateFileSet(FileSet *fc_fileset, const char *fc_name)
{
	BufFile    *fc_file;

	fc_file = fc_makeBufFileCommon(1);
	fc_file->fileset = fc_fileset;
	fc_file->name = pstrdup(fc_name);
	fc_file->files = (File *) palloc(sizeof(File));
	fc_file->files[0] = fc_MakeNewFileSetSegment(fc_file, 0);
	fc_file->readOnly = false;

	return fc_file;
}

/*
 * 打开之前在另一个后端（或本后端）中使用 BufFileCreateFileSet
 * 在同一文件集中的相同名称创建的文件。
 * 创建文件的后端必须调用 BufFileClose() 或
 * BufFileExportFileSet() 以确保该文件准备好被其他
 * 后端打开，并将其设为只读。如果 missing_ok 为真，表明
 * 可以安全地忽略缺失的文件，则如果未找到具有给定名称的 BufFile，
 * 则返回 NULL，否则抛出错误。
 */
BufFile * BufFileOpenFileSet(FileSet *fc_fileset, const char *fc_name, int fc_mode,
				   bool fc_missing_ok)
{
	BufFile    *fc_file;
	char		fc_segment_name[MAXPGPATH];
	Size		fc_capacity = 16;
	File	   *fc_files;
	int			fc_nfiles = 0;

	fc_files = palloc(sizeof(File) * fc_capacity);

	/*
	 * 我们不知道有多少个段，因此我们将探测
	 * 文件系统以找出。
	 */
	for (;;)
	{
		/* 查看我们是否需要扩展文件段数组。 */
		if (fc_nfiles + 1 > fc_capacity)
		{
			fc_capacity *= 2;
			fc_files = repalloc(fc_files, sizeof(File) * fc_capacity);
		}
		/* 尝试加载段。 */
		fc_FileSetSegmentName(fc_segment_name, fc_name, fc_nfiles);
		fc_files[fc_nfiles] = FileSetOpen(fc_fileset, fc_segment_name, fc_mode);
		if (fc_files[fc_nfiles] <= 0)
			break;
		++fc_nfiles;

		CHECK_FOR_INTERRUPTS();
	}

	/*
	 * 如果我们根本没有找到任何文件，则不存在具有此
	 * 名称的 BufFile。
	 */
	if (fc_nfiles == 0)
	{
		/* 释放内存 */
		pfree(fc_files);

		if (fc_missing_ok)
			return NULL;

		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not open temporary file \"%s\" from BufFile \"%s\": %m",
						fc_segment_name, fc_name)));
	}

	fc_file = fc_makeBufFileCommon(fc_nfiles);
	fc_file->files = fc_files;
	fc_file->readOnly = (fc_mode == O_RDONLY);
	fc_file->fileset = fc_fileset;
	fc_file->name = pstrdup(fc_name);

	return fc_file;
}

/*
 * 使用给定名称在给定 FileSet 中删除由 BufFileCreateFileSet 创建的 BufFile。
 *
 * 使用此函数显式删除文件并不是必需的。它只是作为一种主动删除文件的方式，而不是等待
 * FileSet 被清理。
 *
 * 只有一个后端应该尝试删除给定名称，并且应该知道它存在并已被导出或以其他方式关闭，否则
 * missing_ok 应该传递为 true。
 */
void BufFileDeleteFileSet(FileSet *fc_fileset, const char *fc_name, bool fc_missing_ok)
{
	char		fc_segment_name[MAXPGPATH];
	int			fc_segment = 0;
	bool		fc_found = false;

	/*
	 * 我们不知道文件有多少段。我们将继续删除，直到用尽为止。如果我们连一个初始段都找不到，
	 * 则引发错误。
	 */
	for (;;)
	{
		fc_FileSetSegmentName(fc_segment_name, fc_name, fc_segment);
		if (!FileSetDelete(fc_fileset, fc_segment_name, true))
			break;
		fc_found = true;
		++fc_segment;

		CHECK_FOR_INTERRUPTS();
	}

	if (!fc_found && !fc_missing_ok)
		elog(ERROR, "could not delete unknown BufFile \"%s\"", fc_name);
}

/*
 * BufFileExportFileSet --- 刷新并设置为只读，以准备共享。
 */
void BufFileExportFileSet(BufFile *fc_file)
{
	/* 必须是属于 FileSet 的文件。 */
	Assert(fc_file->fileset != NULL);

	/* 如果有人调用两次，这可能是一个错误。 */
	Assert(!fc_file->readOnly);

	fc_BufFileFlush(fc_file);
	fc_file->readOnly = true;
}

/*
 * 关闭 BufFile
 *
 * 与 fclose() 类似，这也隐式地关闭底层文件。
 */
void BufFileClose(BufFile *fc_file)
{
	int			fc_i;

	/* 刷新任何未写入的数据 */
	fc_BufFileFlush(fc_file);
	/* 关闭并删除底层文件 */
	for (fc_i = 0; fc_i < fc_file->numFiles; fc_i++)
		FileClose(fc_file->files[fc_i]);
	/* 释放缓冲区空间 */
	pfree(fc_file->files);
	pfree(fc_file);
}

/*
 * BufFileLoadBuffer
 *
 * 如果可能，从 curOffset 开始将一些数据加载到缓冲区中。
 * 在调用时，必须确保 dirty = false，pos 和 nbytes = 0。
 * 在退出时，nbytes 是加载的字节数。
 */
static void fc_BufFileLoadBuffer(BufFile *fc_file)
{
	File		fc_thisfile;
	instr_time	fc_io_start;
	instr_time	fc_io_time;

	/*
	 * 如果必要且可能，前进到下一个组件文件。
	 */
	if (fc_file->curOffset >= MAX_PHYSICAL_FILESIZE &&
		fc_file->curFile + 1 < fc_file->numFiles)
	{
		fc_file->curFile++;
		fc_file->curOffset = 0L;
	}

	fc_thisfile = fc_file->files[fc_file->curFile];

	if (track_io_timing)
		INSTR_TIME_SET_CURRENT(fc_io_start);

	/*
	 * 读取我们能获取的任何内容，直到完整缓冲区加载为止。
	 */
	fc_file->nbytes = FileRead(fc_thisfile,
							fc_file->buffer.data,
							sizeof(fc_file->buffer.data),
							fc_file->curOffset,
							WAIT_EVENT_BUFFILE_READ);
	if (fc_file->nbytes < 0)
	{
		fc_file->nbytes = 0;
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not read file \"%s\": %m",
						FilePathName(fc_thisfile))));
	}

	if (track_io_timing)
	{
		INSTR_TIME_SET_CURRENT(fc_io_time);
		INSTR_TIME_SUBTRACT(fc_io_time, fc_io_start);
		INSTR_TIME_ADD(pgBufferUsage.temp_blk_read_time, fc_io_time);
	}

	/* 我们选择不在这里前进 curOffset */

	if (fc_file->nbytes > 0)
		pgBufferUsage.temp_blks_read++;
}

/*
 * BufFileDumpBuffer
 *
 * 从 curOffset 开始转储缓冲区内容。
 * 在调用时，应该确保 dirty = true，nbytes > 0。
 * 在退出时，若成功写入，则 dirty 被清除，curOffset 被推进。
 */
static void fc_BufFileDumpBuffer(BufFile *fc_file)
{
	int			fc_wpos = 0;
	int			fc_bytestowrite;
	File		fc_thisfile;

	/*
	 * 与 BufFileLoadBuffer 不同，我们必须转储整个缓冲区，即使它
	 * 跨越组件文件边界；因此我们需要一个循环。
	 */
	while (fc_wpos < fc_file->nbytes)
	{
		off_t		fc_availbytes;
		instr_time	fc_io_start;
		instr_time	fc_io_time;

		/*
		 * 如果必要且可能，前进到下一个组件文件。
		 */
		if (fc_file->curOffset >= MAX_PHYSICAL_FILESIZE)
		{
			while (fc_file->curFile + 1 >= fc_file->numFiles)
				fc_extendBufFile(fc_file);
			fc_file->curFile++;
			fc_file->curOffset = 0L;
		}

		/*
		 * 确定我们需要写入此文件的多少内容。
		 */
		fc_bytestowrite = fc_file->nbytes - fc_wpos;
		fc_availbytes = MAX_PHYSICAL_FILESIZE - fc_file->curOffset;

		if ((off_t) fc_bytestowrite > fc_availbytes)
			fc_bytestowrite = (int) fc_availbytes;

		fc_thisfile = fc_file->files[fc_file->curFile];

		if (track_io_timing)
			INSTR_TIME_SET_CURRENT(fc_io_start);

		fc_bytestowrite = FileWrite(fc_thisfile,
								 fc_file->buffer.data + fc_wpos,
								 fc_bytestowrite,
								 fc_file->curOffset,
								 WAIT_EVENT_BUFFILE_WRITE);
		if (fc_bytestowrite <= 0)
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not write to file \"%s\": %m",
							FilePathName(fc_thisfile))));

		if (track_io_timing)
		{
			INSTR_TIME_SET_CURRENT(fc_io_time);
			INSTR_TIME_SUBTRACT(fc_io_time, fc_io_start);
			INSTR_TIME_ADD(pgBufferUsage.temp_blk_write_time, fc_io_time);
		}

		fc_file->curOffset += fc_bytestowrite;
		fc_wpos += fc_bytestowrite;

		pgBufferUsage.temp_blks_written++;
	}
	fc_file->dirty = false;

	/*
	 * 此时，curOffset 已推进到缓冲区的末尾，
	 * 即其原始值 + nbytes。我们需要使其指向
	 * 逻辑文件位置，即原始值 + pos，以防
	 * 该值更小（因为可能由于在脏缓冲区中的小向后查找而发生！）
	 */
	fc_file->curOffset -= (fc_file->nbytes - fc_file->pos);
	if (fc_file->curOffset < 0)	/* 处理可能的段跨越 */
	{
		fc_file->curFile--;
		Assert(fc_file->curFile >= 0);
		fc_file->curOffset += MAX_PHYSICAL_FILESIZE;
	}

	/*
	 * 现在我们可以在不改变逻辑位置的情况下将缓冲区设置为空
	 */
	fc_file->pos = 0;
	fc_file->nbytes = 0;
}

/*
 * BufFileRead
 *
 * 与 fread() 类似，除了我们假设 1 字节元素大小，并通过
 * ereport() 报告 I/O 错误。
 */
size_t BufFileRead(BufFile *fc_file, void *fc_ptr, size_t fc_size)
{
	size_t		fc_nread = 0;
	size_t		fc_nthistime;

	fc_BufFileFlush(fc_file);

	while (fc_size > 0)
	{
		if (fc_file->pos >= fc_file->nbytes)
		{
			/* 尝试将更多数据加载到缓冲区。 */
			fc_file->curOffset += fc_file->pos;
			fc_file->pos = 0;
			fc_file->nbytes = 0;
			fc_BufFileLoadBuffer(fc_file);
			if (fc_file->nbytes <= 0)
				break;			/* 没有更多可用数据 */
		}

		fc_nthistime = fc_file->nbytes - fc_file->pos;
		if (fc_nthistime > fc_size)
			fc_nthistime = fc_size;
		Assert(fc_nthistime > 0);

		memcpy(fc_ptr, fc_file->buffer.data + fc_file->pos, fc_nthistime);

		fc_file->pos += fc_nthistime;
		fc_ptr = (void *) ((char *) fc_ptr + fc_nthistime);
		fc_size -= fc_nthistime;
		fc_nread += fc_nthistime;
	}

	return fc_nread;
}

/*
 * BufFileWrite
 *
 * 与 fwrite() 类似，除了我们假设 1 字节元素大小，并通过
 * ereport() 报告错误。
 */
void BufFileWrite(BufFile *fc_file, void *fc_ptr, size_t fc_size)
{
	size_t		fc_nthistime;

	Assert(!fc_file->readOnly);

	while (fc_size > 0)
	{
		if (fc_file->pos >= BLCKSZ)
		{
			/* 缓冲区满了，抛弃它 */
			if (fc_file->dirty)
				fc_BufFileDumpBuffer(fc_file);
			else
			{
				/* 嗯，直接从读取跳到写入了？ */
				fc_file->curOffset += fc_file->pos;
				fc_file->pos = 0;
				fc_file->nbytes = 0;
			}
		}

		fc_nthistime = BLCKSZ - fc_file->pos;
		if (fc_nthistime > fc_size)
			fc_nthistime = fc_size;
		Assert(fc_nthistime > 0);

		memcpy(fc_file->buffer.data + fc_file->pos, fc_ptr, fc_nthistime);

		fc_file->dirty = true;
		fc_file->pos += fc_nthistime;
		if (fc_file->nbytes < fc_file->pos)
			fc_file->nbytes = fc_file->pos;
		fc_ptr = (void *) ((char *) fc_ptr + fc_nthistime);
		fc_size -= fc_nthistime;
	}
}

/*
 * BufFileFlush
 *
 * 与 fflush() 类似，除了 I/O 错误通过 ereport() 报告。
 */
static void fc_BufFileFlush(BufFile *fc_file)
{
	if (fc_file->dirty)
		fc_BufFileDumpBuffer(fc_file);

	Assert(!fc_file->dirty);
}

/*
 * BufFileSeek
 *
 * 与 fseek() 类似，除了目标位置需要两个值，以便在逻辑文件大小超过
 * off_t 可表示的最大值时工作。
 * 然而，我们不支持跨越超过该值的相对查找。
 * I/O 错误由 ereport() 报告。
 *
 * 如果 OK，则结果为 0，如果不则为 EOF。如果尝试不可能的查找，则逻辑位置不移动。
 */
int BufFileSeek(BufFile *fc_file, int fc_fileno, off_t fc_offset, int fc_whence)
{
	int			fc_newFile;
	off_t		fc_newOffset;

	switch (fc_whence)
	{
		case SEEK_SET:
			if (fc_fileno < 0)
				return EOF;
			fc_newFile = fc_fileno;
			fc_newOffset = fc_offset;
			break;
		case SEEK_CUR:

			/*
			 * 相对查找仅考虑符号偏移量，忽略
			 * fileno。请注意，大偏移量 (> 1 GB) 风险在此
			 * 加法中溢出，除非我们有 64 位 off_t。
			 */
			fc_newFile = fc_file->curFile;
			fc_newOffset = (fc_file->curOffset + fc_file->pos) + fc_offset;
			break;
		case SEEK_END:

			/*
			 * 最后一个文件的文件大小给我们该
			 * 文件的结束偏移量。
			 */
			fc_newFile = fc_file->numFiles - 1;
			fc_newOffset = FileSize(fc_file->files[fc_file->numFiles - 1]);
			if (fc_newOffset < 0)
				ereport(ERROR,
						(errcode_for_file_access(),
						 errmsg("could not determine size of temporary file \"%s\" from BufFile \"%s\": %m",
								FilePathName(fc_file->files[fc_file->numFiles - 1]),
								fc_file->name)));
			break;
		default:
			elog(ERROR, "invalid whence: %d", fc_whence);
			return EOF;
	}
	while (fc_newOffset < 0)
	{
		if (--fc_newFile < 0)
			return EOF;
		fc_newOffset += MAX_PHYSICAL_FILESIZE;
	}
	if (fc_newFile == fc_file->curFile &&
		fc_newOffset >= fc_file->curOffset &&
		fc_newOffset <= fc_file->curOffset + fc_file->nbytes)
	{
		/*
		 * 查找是在现有缓冲区内的某个点；我们只需调整
		 * pos-within-buffer，而无需刷新缓冲区。请注意，无论是读取
		 * 还是写入，这都是可以的，但如果我们正在写入，缓冲区仍然保持脏。
		 */
		fc_file->pos = (int) (fc_newOffset - fc_file->curOffset);
		return 0;
	}
	/* 否则，必须重新定位缓冲区，因此刷新任何脏数据 */
	fc_BufFileFlush(fc_file);

	/* 
	 * 在这一点上，且不早于此，检查是否超出最后一个段。上面的刷新可能已创建了一个新段，因此在此之前检查将不工作（至少在这段代码中无效）。 
	 */

	/* 将seek转换为“下一个段的开始”到“最后一个段的结束” */
	if (fc_newFile == fc_file->numFiles && fc_newOffset == 0)
	{
		fc_newFile--;
		fc_newOffset = MAX_PHYSICAL_FILESIZE;
	}
	while (fc_newOffset > MAX_PHYSICAL_FILESIZE)
	{
		if (++fc_newFile >= fc_file->numFiles)
			return EOF;
		fc_newOffset -= MAX_PHYSICAL_FILESIZE;
	}
	if (fc_newFile >= fc_file->numFiles)
		return EOF;
	/* Seek是可以的！ */
	fc_file->curFile = fc_newFile;
	fc_file->curOffset = fc_newOffset;
	fc_file->pos = 0;
	fc_file->nbytes = 0;
	return 0;
}

void BufFileTell(BufFile *fc_file, int *fc_fileno, off_t *fc_offset)
{
	*fc_fileno = fc_file->curFile;
	*fc_offset = fc_file->curOffset + fc_file->pos;
}

/* 
 * BufFileSeekBlock --- 基于块的寻址
 *
 * 执行绝对的寻址，定位到文件的第n个BLCKSZ大小的块的开始。 注意，使用此接口的用户如果他们的文件超过BLCKSZ * LONG_MAX字节将会失败，但那相当多；我们也不处理比这更大的表...
 *
 * 如果正常则结果为0，如果不则为EOF。如果尝试了不可能的寻址，逻辑位置不会移动。
 */
int BufFileSeekBlock(BufFile *fc_file, long fc_blknum)
{
	return BufFileSeek(fc_file,
					   (int) (fc_blknum / BUFFILE_SEG_SIZE),
					   (off_t) (fc_blknum % BUFFILE_SEG_SIZE) * BLCKSZ,
					   SEEK_SET);
}

#ifdef NOT_USED
/* 
 * BufFileTellBlock --- 基于块的告知
 *
 * 当前寻址位置的块中任何小数部分都将被忽略。
 */
long BufFileTellBlock(BufFile *fc_file)
{
	long		fc_blknum;

	fc_blknum = (fc_file->curOffset + fc_file->pos) / BLCKSZ;
	fc_blknum += fc_file->curFile * BUFFILE_SEG_SIZE;
	return fc_blknum;
}

#endif

/* 
 * 返回当前基于文件集的BufFile大小。
 *
 * 将BufFileAppend所留下的任何空洞计入大小。
 * 失败时会ereport()。
 */
int64 BufFileSize(BufFile *fc_file)
{
	int64		fc_lastFileSize;

	Assert(fc_file->fileset != NULL);

	/* 获取最后一个物理文件的大小。 */
	fc_lastFileSize = FileSize(fc_file->files[fc_file->numFiles - 1]);
	if (fc_lastFileSize < 0)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not determine size of temporary file \"%s\" from BufFile \"%s\": %m",
						FilePathName(fc_file->files[fc_file->numFiles - 1]),
						fc_file->name)));

	return ((fc_file->numFiles - 1) * (int64) MAX_PHYSICAL_FILESIZE) +
		fc_lastFileSize;
}

/* 
 * 将源文件（在文件集内部管理）的内容附加到目标文件（在同一文件集内部管理）的末尾。
 *
 * 注意该操作吸收了来自“源”的底层资源的所有权。 调用者在首先调用这里之后不应再次调用BufFileClose针对源。 
 * 源和目标的资源所有者也必须匹配。
 *
 * 此操作通过操作段文件的列表来工作，因此文件内容总是附加在MAX_PHYSICAL_FILESIZE对齐的边界上，通常在边界之前创建空漏洞。 这些区域不包含任何有趣的数据，并且调用者无法读取。
 *
 * 返回目标中源内容开始的块号。 调用者在处理基于原始BufFile空间的块位置时应将其作为偏移量应用。
 */
long BufFileAppend(BufFile *fc_target, BufFile *fc_source)
{
	long		fc_startBlock = fc_target->numFiles * BUFFILE_SEG_SIZE;
	int			fc_newNumFiles = fc_target->numFiles + fc_source->numFiles;
	int			fc_i;

	Assert(fc_target->fileset != NULL);
	Assert(fc_source->readOnly);
	Assert(!fc_source->dirty);
	Assert(fc_source->fileset != NULL);

	if (fc_target->resowner != fc_source->resowner)
		elog(ERROR, "could not append BufFile with non-matching resource owner");

	fc_target->files = (File *)
		repalloc(fc_target->files, sizeof(File) * fc_newNumFiles);
	for (fc_i = fc_target->numFiles; fc_i < fc_newNumFiles; fc_i++)
		fc_target->files[fc_i] = fc_source->files[fc_i - fc_target->numFiles];
	fc_target->numFiles = fc_newNumFiles;

	return fc_startBlock;
}

/* 
 * 截断由BufFileCreateFileSet创建的BufFile，直到给定的fileno和偏移量。
 */
void BufFileTruncateFileSet(BufFile *fc_file, int fc_fileno, off_t fc_offset)
{
	int			fc_numFiles = fc_file->numFiles;
	int			fc_newFile = fc_fileno;
	off_t		fc_newOffset = fc_file->curOffset;
	char		fc_segment_name[MAXPGPATH];
	int			fc_i;

	/* 
	 * 遍历所有文件，直到给定的fileno，移除大于fileno的文件，并截断给定的文件直到偏移量。 注意，如果偏移量为0，我们也会移除给定的fileno，只要它不是我们截断的第一个文件。
	 */
	for (fc_i = fc_file->numFiles - 1; fc_i >= fc_fileno; fc_i--)
	{
		if ((fc_i != fc_fileno || fc_offset == 0) && fc_i != 0)
		{
			fc_FileSetSegmentName(fc_segment_name, fc_file->name, fc_i);
			FileClose(fc_file->files[fc_i]);
			if (!FileSetDelete(fc_file->fileset, fc_segment_name, true))
				ereport(ERROR,
						(errcode_for_file_access(),
						 errmsg("could not delete fileset \"%s\": %m",
								fc_segment_name)));
			fc_numFiles--;
			fc_newOffset = MAX_PHYSICAL_FILESIZE;

			/* 
			 * 这要求表明我们已删除给定的fileno。
			 */
			if (fc_i == fc_fileno)
				fc_newFile--;
		}
		else
		{
			if (FileTruncate(fc_file->files[fc_i], fc_offset,
							 WAIT_EVENT_BUFFILE_TRUNCATE) < 0)
				ereport(ERROR,
						(errcode_for_file_access(),
						 errmsg("could not truncate file \"%s\": %m",
								FilePathName(fc_file->files[fc_i]))));
			fc_newOffset = fc_offset;
		}
	}

	fc_file->numFiles = fc_numFiles;

	/* 
	 * 如果截断点在现有缓冲区内，则我们可以只调整缓冲区内的pos。
	 */
	if (fc_newFile == fc_file->curFile &&
		fc_newOffset >= fc_file->curOffset &&
		fc_newOffset <= fc_file->curOffset + fc_file->nbytes)
	{
		/* 如果新位置更大，则不需要重置当前pos。 */
		if (fc_newOffset <= fc_file->curOffset + fc_file->pos)
			fc_file->pos = (int) (fc_newOffset - fc_file->curOffset);

		/* 调整当前缓冲区的字节数。 */
		fc_file->nbytes = (int) (fc_newOffset - fc_file->curOffset);
	}
	else if (fc_newFile == fc_file->curFile &&
			 fc_newOffset < fc_file->curOffset)
	{
		/* 
		 * 截断点在现有文件内，但在当前位之前，因此我们可以忘记当前缓冲区并重置当前位。
		 */
		fc_file->curOffset = fc_newOffset;
		fc_file->pos = 0;
		fc_file->nbytes = 0;
	}
	else if (fc_newFile < fc_file->curFile)
	{
		/* 
		 * 截断点在当前文件之前，因此需要相应地重置当前位。
		 */
		fc_file->curFile = fc_newFile;
		fc_file->curOffset = fc_newOffset;
		fc_file->pos = 0;
		fc_file->nbytes = 0;
	}
	/* 如果截断点超出当前文件，则无需操作。 */
}
